/** * Oshi (https://github.com/oshi/oshi) * * Copyright (c) 2010 - 2017 The Oshi Project Team * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Maintainers: * dblock[at]dblock[dot]org * widdis[at]gmail[dot]com * enrico.bianchi[at]gmail[dot]com * * Contributors: * https://github.com/oshi/oshi/graphs/contributors */ /** i * Oshi (https://github.com/dblock/oshi) * * Copyright (c) 2010 - 2017 The Oshi Project Team * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Maintainers: * dblock[at]dblock[dot]org * widdis[at]gmail[dot]com * enrico.bianchi[at]gmail[dot]com * * Contributors: * https://github.com/dblock/oshi/graphs/contributors */ package oshi.util.platform.windows; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.OleAuto; import com.sun.jna.platform.win32.Variant.VARIANT; import com.sun.jna.platform.win32.WTypes.BSTR; import com.sun.jna.platform.win32.WinNT.HRESULT; import com.sun.jna.platform.win32.COM.COMUtils; import com.sun.jna.ptr.LongByReference; import com.sun.jna.ptr.PointerByReference; import oshi.jna.platform.windows.Ole32; import oshi.jna.platform.windows.COM.EnumWbemClassObject; import oshi.jna.platform.windows.COM.WbemClassObject; import oshi.jna.platform.windows.COM.WbemLocator; import oshi.jna.platform.windows.COM.WbemServices; import oshi.util.FormatUtil; import oshi.util.ParseUtil; /** * Provides access to WMI queries * * @author widdis[at]gmail[dot]com */ public class WmiUtil { private static final Logger LOG = LoggerFactory.getLogger(WmiUtil.class); public static final String DEFAULT_NAMESPACE = "ROOT\\CIMV2"; private static boolean comInitialized = false; private static boolean securityInitialized = false; /** * Enum for WMI queries for proper parsing from the returned VARIANT */ public enum ValueType { // Properties STRING, UINT32, FLOAT, DATETIME, BOOLEAN, UINT64, UINT16, // Methods (use "__PATH" for property) PROCESS_GETOWNER, PROCESS_GETOWNERSID } /** * For WMI queries requiring array input */ private static final ValueType[] STRING_TYPE = { ValueType.STRING }; private static final ValueType[] UINT32_TYPE = { ValueType.UINT32 }; private static final ValueType[] FLOAT_TYPE = { ValueType.FLOAT }; /** * Determine if WMI has the requested namespace. Some namespaces only exist * on newer versions of Windows. * * @param namespace * The namespace to test * @return true if the namespace exists, false otherwise */ public static boolean hasNamespace(String namespace) { Map<String, List<String>> nsMap = WmiUtil.selectStringsFrom("ROOT", "__NAMESPACE", "Name", null); for (String s : nsMap.get("Name")) { if (s.equals(namespace)) { return true; } } return false; } /** * Get a single Unsigned Integer value from WMI (as Long) * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param property * The property whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @return A Long containing the value of the requested property */ public static Long selectUint32From(String namespace, String wmiClass, String property, String whereClause) { Map<String, List<Object>> result = queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, property, wmiClass, whereClause, UINT32_TYPE); if (result.containsKey(property) && !result.get(property).isEmpty()) { return (Long) result.get(property).get(0); } return 0L; } /** * Get multiple Unsigned Integer values from WMI (as Longs) * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param properties * A comma delimited list of properties whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @return A map, with each property as the key, containing Longs with the * value of the requested properties. Each list's order corresponds * to other lists. */ public static Map<String, List<Long>> selectUint32sFrom(String namespace, String wmiClass, String properties, String whereClause) { Map<String, List<Object>> result = queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, properties, wmiClass, whereClause, UINT32_TYPE); HashMap<String, List<Long>> longMap = new HashMap<>(); for (Entry<String, List<Object>> entry : result.entrySet()) { ArrayList<Long> longList = new ArrayList<>(); for (Object obj : entry.getValue()) { longList.add((Long) obj); } longMap.put(entry.getKey(), longList); } return longMap; } /** * Get a single Float value from WMI * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param property * The property whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @return A Float containing the value of the requested property */ public static Float selectFloatFrom(String namespace, String wmiClass, String property, String whereClause) { Map<String, List<Object>> result = queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, property, wmiClass, whereClause, FLOAT_TYPE); if (result.containsKey(property) && !result.get(property).isEmpty()) { return (Float) result.get(property).get(0); } return 0f; } /** * Get multiple Float values from WMI * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param properties * A comma delimited list of properties whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @return A map, with each property as the key, containing Floats with the * value of the requested properties. Each list's order corresponds * to other lists. */ public static Map<String, List<Float>> selectFloatsFrom(String namespace, String wmiClass, String properties, String whereClause) { Map<String, List<Object>> result = queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, properties, wmiClass, whereClause, FLOAT_TYPE); HashMap<String, List<Float>> floatMap = new HashMap<>(); for (Entry<String, List<Object>> entry : result.entrySet()) { ArrayList<Float> floatList = new ArrayList<>(); for (Object obj : entry.getValue()) { floatList.add((Float) obj); } floatMap.put(entry.getKey(), floatList); } return floatMap; } /** * Get a single String value from WMI * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param property * The property whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @return A string containing the value of the requested property */ public static String selectStringFrom(String namespace, String wmiClass, String property, String whereClause) { Map<String, List<Object>> result = queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, property, wmiClass, whereClause, STRING_TYPE); if (result.containsKey(property) && !result.get(property).isEmpty()) { return (String) result.get(property).get(0); } return ""; } /** * Get multiple String values from WMI * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param properties * A comma delimited list of properties whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @return A map, with each property as the key, containing strings with the * value of the requested properties. Each list's order corresponds * to other lists. */ public static Map<String, List<String>> selectStringsFrom(String namespace, String wmiClass, String properties, String whereClause) { Map<String, List<Object>> result = queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, properties, wmiClass, whereClause, STRING_TYPE); HashMap<String, List<String>> strMap = new HashMap<>(); for (Entry<String, List<Object>> entry : result.entrySet()) { ArrayList<String> strList = new ArrayList<>(); for (Object obj : entry.getValue()) { strList.add((String) obj); } strMap.put(entry.getKey(), strList); } return strMap; } /** * Get multiple individually typed values from WMI * * @param namespace * The namespace or null to use the default * @param wmiClass * The class to query * @param properties * A comma delimited list of properties whose value to return * @param whereClause * A WQL where clause matching properties and keywords * @param propertyTypes * An array of types corresponding to the properties, or a single * element array * @return A map, with each property as the key, containing Objects with the * value of the requested properties. Each list's order corresponds * to other lists. The type of the objects is identified by the * propertyTypes array. If only one propertyType is given, all * Objects will have that type. It is the responsibility of the * caller to cast the returned objects. */ public static Map<String, List<Object>> selectObjectsFrom(String namespace, String wmiClass, String properties, String whereClause, ValueType[] propertyTypes) { return queryWMI(namespace == null ? DEFAULT_NAMESPACE : namespace, properties, wmiClass, whereClause, propertyTypes); } /** * Query WMI for values * * @param namespace * The namespace to query * @param properties * A single property or comma-delimited list of properties to * enumerate * @param wmiClass * The WMI class to query * @param propertyTypes * An array corresponding to the properties, containing the type * of data being queried, to control how VARIANT is parsed * @return A map, with the string value of each property as the key, * containing a list of Objects which can be cast appropriately per * valType. The order of objects in each list corresponds to the * other lists. */ private static Map<String, List<Object>> queryWMI(String namespace, String properties, String wmiClass, String whereClause, ValueType[] propertyTypes) { // Set up empty map Map<String, List<Object>> values = new HashMap<>(); String[] props = properties.split(","); for (int i = 0; i < props.length; i++) { if ("__PATH".equals(props[i])) { // Methods will query __PATH values.put(propertyTypes[i].name(), new ArrayList<>()); } else { // Properties are named values.put(props[i], new ArrayList<>()); } } // Initialize COM if (!initCOM()) { unInitCOM(); return values; } PointerByReference pSvc = new PointerByReference(); if (!connectServer(namespace, pSvc)) { unInitCOM(); return values; } WbemServices svc = new WbemServices(pSvc.getValue()); PointerByReference pEnumerator = new PointerByReference(); if (!selectProperties(svc, pEnumerator, properties, wmiClass, whereClause)) { svc.Release(); unInitCOM(); return values; } EnumWbemClassObject enumerator = new EnumWbemClassObject(pEnumerator.getValue()); enumerateProperties(values, enumerator, props, propertyTypes, svc); // Cleanup enumerator.Release(); svc.Release(); unInitCOM(); return values; } /* * Below methods ported from: Getting WMI Data from Local Computer * https://msdn.microsoft.com/en-us/library/aa390423(v=VS.85).aspx * * Steps 1 - 7 correspond to the above link. */ /** * Initializes COM library and sets security to impersonate the local user * * @return true if COM successfully initialized */ private static boolean initCOM() { // Step 1: -------------------------------------------------- // Initialize COM. ------------------------------------------ HRESULT hres = Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_MULTITHREADED); if (COMUtils.FAILED(hres)) { if (hres.intValue() == Ole32.RPC_E_CHANGED_MODE) { // Com already initialized, ignore error LOG.debug("COM already initialized."); securityInitialized = true; return true; } LOG.error(String.format("Failed to initialize COM library. Error code = 0x%08x", hres.intValue())); return false; } comInitialized = true; if (securityInitialized) { // Only run CoInitializeSecuirty once return true; } // Step 2: -------------------------------------------------- // Set general COM security levels -------------------------- hres = Ole32.INSTANCE.CoInitializeSecurity(null, new NativeLong(-1), null, null, Ole32.RPC_C_AUTHN_LEVEL_DEFAULT, Ole32.RPC_C_IMP_LEVEL_IMPERSONATE, null, Ole32.EOAC_NONE, null); if (COMUtils.FAILED(hres) && hres.intValue() != Ole32.RPC_E_TOO_LATE) { LOG.error(String.format("Failed to initialize security. Error code = 0x%08x", hres.intValue())); Ole32.INSTANCE.CoUninitialize(); return false; } securityInitialized = true; return true; } /** * Obtains a locator to the WMI server and connects to the specified * namespace * * @param namespace * The namespace to connect to * @param pSvc * A pointer to receive an indirect to the WMI service * @return true if successful; pSvc will contain an indirect pointer to the * WMI service for future IWbemServices calls */ private static boolean connectServer(String namespace, PointerByReference pSvc) { // Step 3: --------------------------------------------------- // Obtain the initial locator to WMI ------------------------- WbemLocator loc = WbemLocator.create(); if (loc == null) { return false; } // Step 4: ----------------------------------------------------- // Connect to WMI through the IWbemLocator::ConnectServer method // Connect to the namespace with the current user and obtain pointer // pSvc to make IWbemServices calls. HRESULT hres = loc.ConnectServer(new BSTR(namespace), null, null, null, null, null, null, pSvc); if (COMUtils.FAILED(hres)) { LOG.error(String.format("Could not connect to namespace %s. Error code = 0x%08x", namespace, hres.intValue())); loc.Release(); unInitCOM(); return false; } LOG.debug("Connected to {} WMI namespace", namespace); loc.Release(); // Step 5: -------------------------------------------------- // Set security levels on the proxy ------------------------- hres = Ole32.INSTANCE.CoSetProxyBlanket(pSvc.getValue(), Ole32.RPC_C_AUTHN_WINNT, Ole32.RPC_C_AUTHZ_NONE, null, Ole32.RPC_C_AUTHN_LEVEL_CALL, Ole32.RPC_C_IMP_LEVEL_IMPERSONATE, null, Ole32.EOAC_NONE); if (COMUtils.FAILED(hres)) { LOG.error(String.format("Could not set proxy blanket. Error code = 0x%08x", hres.intValue())); new WbemServices(pSvc.getValue()).Release(); unInitCOM(); return false; } return true; } /** * Selects properties from WMI. Returns immediately, even while results are * being retrieved; results may begun to be enumerated in the forward * direction only. * * @param svc * A WbemServices object to make the calls * @param pEnumerator * An enumerator to receive the results of the query * @param properties * A comma separated list of properties to query * @param wmiClass * The WMI class to query * @param whereClause * A WHERE clause to narrow the query * @return True if successful. The enumerator will allow enumeration of * results of the query */ private static boolean selectProperties(WbemServices svc, PointerByReference pEnumerator, String properties, String wmiClass, String whereClause) { // Step 6: -------------------------------------------------- // Use the IWbemServices pointer to make requests of WMI ---- String query = String.format("SELECT %s FROM %s %s", properties, wmiClass, whereClause != null ? whereClause : ""); LOG.debug("Query: {}", query); HRESULT hres = svc.ExecQuery(new BSTR("WQL"), new BSTR(query), new NativeLong( EnumWbemClassObject.WBEM_FLAG_FORWARD_ONLY | EnumWbemClassObject.WBEM_FLAG_RETURN_IMMEDIATELY), null, pEnumerator); if (COMUtils.FAILED(hres)) { LOG.error(String.format("Query '%s' failed. Error code = 0x%08x", query, hres.intValue())); svc.Release(); unInitCOM(); return false; } return true; } /** * Enumerate the results of a WMI query. This method is called while results * are still being retrieved and may iterate in the forward direction only. * * @param values * A map to hold the results of the query using the property as * the key, and placing each enumerated result in a * (common-index) list for each property * @param enumerator * The enumerator with the results * @param properties * Comma-delimited list of properties to retrieve * @param propertyTypes * An array of property types matching the properties or a single * property type which will be used for all properties * @param svc * The WbemServices object */ private static void enumerateProperties(Map<String, List<Object>> values, EnumWbemClassObject enumerator, String[] properties, ValueType[] propertyTypes, WbemServices svc) { if (propertyTypes.length > 1 && properties.length != propertyTypes.length) { throw new IllegalArgumentException("Property type array size must be 1 or equal to properties array size."); } // Step 7: ------------------------------------------------- // Get the data from the query in step 6 ------------------- PointerByReference pclsObj = new PointerByReference(); LongByReference uReturn = new LongByReference(0L); while (enumerator.getPointer() != Pointer.NULL) { HRESULT hres = enumerator.Next(new NativeLong(EnumWbemClassObject.WBEM_INFINITE), new NativeLong(1), pclsObj, uReturn); // Requested 1; if 0 objects returned, we're done if (0L == uReturn.getValue() || COMUtils.FAILED(hres)) { // Enumerator will be released by calling method so no need to // release it here. return; } VARIANT.ByReference vtProp = new VARIANT.ByReference(); // Get the value of the properties WbemClassObject clsObj = new WbemClassObject(pclsObj.getValue()); for (int p = 0; p < properties.length; p++) { String property = properties[p]; hres = clsObj.Get(new BSTR(property), new NativeLong(0L), vtProp, null, null); ValueType propertyType = propertyTypes.length > 1 ? propertyTypes[p] : propertyTypes[0]; switch (propertyType) { case STRING: values.get(property).add(vtProp.getValue() == null ? "unknown" : vtProp.stringValue()); break; // uint16 == VT_I4, a 32-bit number case UINT16: values.get(property).add(vtProp.getValue() == null ? 0L : vtProp.intValue()); break; // WMI Uint32s will return as longs case UINT32: values.get(property).add(vtProp.getValue() == null ? 0L : vtProp.longValue()); break; // WMI Longs will return as strings so we have the option of // calling a string and parsing later, or calling UINT64 and // letting this method do the parsing case UINT64: values.get(property).add( vtProp.getValue() == null ? 0L : ParseUtil.parseLongOrDefault(vtProp.stringValue(), 0L)); break; case FLOAT: values.get(property).add(vtProp.getValue() == null ? 0f : vtProp.floatValue()); break; case DATETIME: // Read a string in format 20160513072950.782000-420 and // parse to a long representing ms since eopch values.get(property) .add(vtProp.getValue() == null ? 0L : ParseUtil.cimDateTimeToMillis(vtProp.stringValue())); break; case BOOLEAN: values.get(property).add(vtProp.getValue() == null ? 0L : vtProp.booleanValue()); break; case PROCESS_GETOWNER: // Win32_Process object GetOwner method String owner = FormatUtil.join("\\", execMethod(svc, vtProp.stringValue(), "GetOwner", "Domain", "User")); values.get(propertyType.name()).add("\\".equals(owner) ? "N/A" : owner); break; case PROCESS_GETOWNERSID: // Win32_Process object GetOwnerSid method String[] ownerSid = execMethod(svc, vtProp.stringValue(), "GetOwnerSid", "Sid"); values.get(propertyType.name()).add(ownerSid.length < 1 ? "" : ownerSid[0]); break; default: // Should never get here! If you get this exception you've // added something to the enum without adding it here. Tsk. throw new IllegalArgumentException("Unimplemented enum type: " + propertyType.toString()); } OleAuto.INSTANCE.VariantClear(vtProp); } clsObj.Release(); } } /** * UnInitializes COM library if it was initialized by the {@link #initCOM()} * method. Otherwise, does nothing. */ private static void unInitCOM() { if (comInitialized) { Ole32.INSTANCE.CoUninitialize(); comInitialized = false; } } /** * Convenience method for executing WMI methods without any input parameters * * @param svc * The WbemServices object * @param clsObj * The full path to the class object to execute (result of WMI * "__PATH" query) * @param method * The name of the method to execute * @param properties * One or more properties returned as a result of the query * @return An array of the properties returned from the method */ private static String[] execMethod(WbemServices svc, String clsObj, String method, String... properties) { List<String> result = new ArrayList<>(); PointerByReference ppOutParams = new PointerByReference(); HRESULT hres = svc.ExecMethod(new BSTR(clsObj), new BSTR(method), new NativeLong(0L), null, null, ppOutParams, null); if (COMUtils.FAILED(hres)) { return new String[0]; } WbemClassObject obj = new WbemClassObject(ppOutParams.getValue()); VARIANT.ByReference vtProp = new VARIANT.ByReference(); for (String prop : properties) { hres = obj.Get(new BSTR(prop), new NativeLong(0L), vtProp, null, null); if (!COMUtils.FAILED(hres)) { result.add(vtProp.getValue() == null ? "" : vtProp.stringValue()); } } obj.Release(); return result.toArray(new String[result.size()]); } }